package com.jt.demo02.aop;
/**按照注解进行拦截*/
import com.jt.demo02.anno.Find;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.util.Arrays;

@Component //将当前类交给容器管理
@Aspect    //我是一个切面类
@Order(1)  //可以利用Order关键字 实现AOP执行顺序的排序 1 2 2 1  数字越小越先执行
public class SpringAOP {
    /***切面 = 切入点表达式 + 通知方法
     * 1.切入点:
     *      理解:可以理解为一个if判断
     *      判断条件:切入点表达式
     *      规则:
     *          如果满足表达式 则判断为true , 则执行通知方法
     *          如果不满足表达式 则判断为false , 则不执行通知方法
     * 2.切入点表达式
     *      bean("对象的Id")       每次只拦截一个
     *      within("包名.类名")
     *      execution(返回值类型 包名.类名.方法名(参数列表))
     *      @annotation(注解的路径)
     *
     * 3.切入点表达式案例练习
     *      1.@Pointcut("bean(userServiceImpl)")  只匹配ID为userServiceImpl的对象
     *      2.@Pointcut("within(com.jt.demo02.service.*)")  匹配com.jt.demo02.service下所有对象
     *      说明:上述的操作,匹配粒度是粗粒度,案例匹配
     *      3.@Pointcut("execution(* com.jt.demo02.service..*.*(..))")
     *      * 拦截返回值类型任意 xx.xx.service包下所有子孙包的所有的类的任意方法
     *      @Pointcut("execution(* com.jt.demo02.service..*.add*(..))")
     *      拦截返回值类型任意 xx.xx.service包下所有子孙包的所有的类.以add开头的方法
     *      4. @Pointcut("@annotation(com.jt.demo02.anno.cgb2110)")
     *      拦截xx包下的CGB2110的注解*/

//    @Pointcut("bean(userServiceImpl)")
//    @Pointcut("within(com.jt.demo02.service.*)")
//    @Pointcut("execution(* com.jt.demo02.service..*.add*(..))")
    @Pointcut("@annotation(com.jt.demo02.anno.cgb2110)")

    public void pointcut(){

    }

    /**
     * 定义通知方法
     *  1.前置通知  在目标方法执行之前执行
     *  2.后置通知  在目标方法执行之后执行
     *  3.异常通知  在目标方法执行之后抛出异常时执行
     *  4.最终通知  都要执行的通知
     *  上述四大通知一般用于记录程序状态,只做记录
     *  5.环绕通知  在目标方法执行之前都要执行的通知
     *
     *  记录程序的状态
     *  1.目标对象的class/类路径
     *  2.目标对象的方法名
     *  3.目标对象的方法的参数信息
     *  4.获取目标对象方法的返回值
     *  5.获取目标对象执行报错的异常信息
     */

//    @Before("pointcut()")
    public void before(JoinPoint joinPoint){   //joinPoint 连接点
        System.out.println("前置通知");
        //1.获取目标对象的类型
        Class targetClass = joinPoint.getTarget().getClass();
        //2.获取目标对象的路径
        String path = joinPoint.getSignature().getDeclaringTypeName();
        //3.获取目标对象的方法名称
        String methodName = joinPoint.getSignature().getName();
        //4.获取方法参数
        Object[] args = joinPoint.getArgs();
        //打印输出
        System.out.println("类型:"+targetClass);
        System.out.println("类路径:"+path);
        System.out.println("方法名:"+methodName);
        System.out.println("参数:"+ Arrays.toString(args));
    }

    @AfterReturning(value = "pointcut()",returning = "result")
    public void afterReturn(JoinPoint joinPoint,Object result){
        System.out.println("获取方法的返回值:"+result);
        System.out.println("后置通知");
    }

    @AfterThrowing(value = "pointcut()",throwing = "exception")
    public void afterThow(JoinPoint joinPoint,Exception exception){
//        exception.printStackTrace();//打印异常
        System.out.println("我是异常通知:"+exception.getMessage());//获取异常信息
    }

    @After("pointcut()")
    public void after(){
        System.out.println("我是最终通知");
    }

    /**
     * 环绕通知:
     * 1.特点:
     *      1.方法执行前后,通知都要执行
     *      2.环绕通知可以控制目标方法是否执行
     *      3.环绕通知必须添加返回值
     * porceed()
     *      作用1:如果有下一个通知,则执行下一个通知
     *      作用2:如果没有下一个通知,则执行目标方法
     */

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("环绕通知开始");
        long startTime = System.currentTimeMillis();//开始时间
        Object result = joinPoint.proceed();//执行业务
        long endTime = System.currentTimeMillis();//结束时间
        System.out.println("方法的执行时间:"+(endTime-startTime)+"毫秒");
        System.out.println("环绕通知结束");
        return result;
    }


    /**
     *知识点:
     *      1.如果切入点表达式只对当前通知有效.则可以按照如下方式编辑
     * 要求: 动态的拦截Find注解,并且要获取Find注解中的参数Id
     * 难点: 动态获取注解的对象!!
     * 代码解释:
     *      1.@annotation(find) 拦截find变量名称对应类型的注解
     *      2.当匹配该注解之后,将注解对象当做参数传递给find
     *      优势: 可以一步到位获取注解的内容.避免了反射的代码
     *
     */

//    @Before("@annotation(find)")
    public void before2(Find find){
        System.out.println("ID的值为"+find.id());
    }

}
